package com.tegtech.starter.redis.service;

import java.util.Collections;
import java.util.function.Supplier;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import com.tegtech.common.function.Procedure;

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;


/**
 * 基于redis实现分布式锁（redis单机部署可用）
 * 
 * @Author Jun
 * @date 2020年5月24日 下午3:48:36
 */
@Slf4j
@Component
public class RedisLock {

	private static final int EXPIRE = 5;
	private static final Long SUCCESS = 1L;
	private static final String LOCK_SCRIPT = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
	private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
	
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	
	/**
	 * 加锁，执行无返回值的viSupplier方法
	 * @param lockKey
	 * @param viSupplier
	 */
	public void lock(String lockKey, Procedure procedure) {
		String token = IdUtil.fastSimpleUUID();
		try {
			this.lock(lockKey, token, EXPIRE);
			procedure.run();
		} finally {
			this.unlock(lockKey, token);
		}
	}
	
	/**
	 * 加锁，执行有返回值的Supplier<R>方法
	 * @param <R>
	 * @param lockKey
	 * @param supplier
	 * @return
	 */
	public <R> R rlock(String lockKey, Supplier<R> supplier) {
		String token = IdUtil.fastSimpleUUID();
		try {
			this.lock(lockKey, token, EXPIRE);
			return supplier.get();
		} finally {
			this.unlock(lockKey, token);
		}
	}
	
    /**
     * 获取锁
     * @param lockKey
     * @param value
     * @param expireTime：单位-秒
     * @return
     */
    public boolean lock(String lockKey, String value, int expireTime) {
        try{
            RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
            Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
            return SUCCESS.equals(result);
        }catch(Exception e){
        	log.error("RedisLock.lock exception : {}", e);
        	return false;
        }
    }
 
    /**
     * 释放锁
     * @param lockKey
     * @param value
     * @return
     */
    public boolean unlock(String lockKey, String value) {
        try {
			RedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
			Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),value);
			return SUCCESS.equals(result);
		} catch (Exception e) {
			log.error("RedisLock.unlock exception : {}", e);
			return false;
		}
    }
	
}
